Day30 要來寫個打地鼠的小遊戲
const MOLE_IMAGE_URL =
"https://raw.githubusercontent.com/wesbos/JavaScript30/master/30%20-%20Whack%20A%20Mole/mole.svg";
const DIRT_IMAGE_URL =
"https://raw.githubusercontent.com/wesbos/JavaScript30/refs/heads/master/30%20-%20Whack%20A%20Mole/dirt.svg";
const [score, setScore] = useState<number>(0);
const [timeLeft, setTimeLeft] = useState<number>(10);
const [gameStarted, setGameStarted] = useState<boolean>(false);
const [activeMole, setActiveMole] = useState<number | null>(null);
const timerRef = useRef<number | null>(null);
const moleTimerRef = useRef<number | null>(null);
const startGame = useCallback(() => {
setScore(0);
setTimeLeft(10);
setGameStarted(true);
setActiveMole(null);
const updateTime = () => {
setTimeLeft((prevTime: number) => {
if (prevTime <= 1) {
if (timerRef.current) {
clearInterval(timerRef.current);
}
if (moleTimerRef.current) {
clearInterval(moleTimerRef.current);
}
setGameStarted(false);
setActiveMole(null);
return 0;
}
return prevTime - 1;
});
};
const updateMole = () => {
const newMole = Math.floor(Math.random() * 6);
setActiveMole(newMole);
};
timerRef.current = window.setInterval(updateTime, 1000);
moleTimerRef.current = window.setInterval(updateMole, 1000);
}, []);
const whackMole = useCallback(
(index: number) => {
if (index === activeMole) {
setScore((prevScore: number) => prevScore + 1);
setActiveMole(null);
}
},
[activeMole]
);
const stopGame = useCallback(() => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
if (moleTimerRef.current) {
clearInterval(moleTimerRef.current);
}
setGameStarted(false);
setActiveMole(null);
}, []);
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-green-100">
<h1 className="text-4xl font-bold mb-4">Whack-A-Mole!</h1>
<div className="mb-4">
<span className="font-bold">Score:</span> {score} |{" "}
<span className="font-bold">Time Left:</span> {timeLeft}s
</div>
{!gameStarted ? (
<button
type="button"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={startGame}
>
Start Game
</button>
) : (
<div className="grid grid-cols-3 gap-4">
{[...Array(6)].map((_, index) => (
<div
key={index}
className="w-32 h-32 relative cursor-pointer overflow-hidden"
onClick={() => whackMole(index)}
>
<div
className="absolute inset-0 bg-contain bg-no-repeat bg-bottom z-10"
style={{ backgroundImage: `url(${DIRT_IMAGE_URL})` }}
/>
<div
className={`absolute inset-0 bg-contain bg-no-repeat bg-bottom transition-transform duration-200 ease-out ${
activeMole === index ? "translate-y-0" : "translate-y-full"
}`}
style={{ backgroundImage: `url(${MOLE_IMAGE_URL})` }}
/>
</div>
))}
</div>
)}
{gameStarted && (
<button
type="button"
className="mt-4 bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
onClick={stopGame}
>
Stop Game
</button>
)}
</div>
);